package net.butfly.bus.server.deploy;

import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.naming.NamingException;

import net.butfly.albacore.utils.ReflectionUtils;
import net.butfly.bus.Constants;

import org.apache.commons.beanutils.BeanMap;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.dom4j.Attribute;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.mortbay.jetty.Connector;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.nio.SelectChannelConnector;
import org.mortbay.jetty.plus.naming.Resource;
import org.mortbay.jetty.servlet.HashSessionManager;
import org.mortbay.jetty.servlet.ServletHandler;
import org.mortbay.jetty.servlet.ServletHolder;
import org.mortbay.jetty.servlet.SessionHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class JettyStarter implements Runnable {
	private static final Logger logger = LoggerFactory.getLogger(JettyStarter.class);
	private final static String DEFAULT_CONTEXT_PATH = "/bus";
	private final static int DEFAULT_PORT = 9876;
	private final static int DEFAULT_THREAD_POOL_SIZE = 10;
	private final Server server;
	private boolean running = false;

	public JettyStarter() {
		this(DEFAULT_PORT);
	}

	public JettyStarter(int port) {
		this(port, DEFAULT_THREAD_POOL_SIZE);
	}

	public JettyStarter(int port, int threadPoolSize) {
		this.server = createServer(port, threadPoolSize);
	}

	public void run(boolean fork) {
		if (fork) {
			running = true;
			Thread th = new Thread(this);
			th.setDaemon(true);
			th.setName("Bus-Server-Jetty-Starter-Thread");
			th.start();
			while (running)
				try {
					Thread.sleep(30000);
				} catch (InterruptedException ex) {}
		} else
			this.run();
	}

	@Override
	public void run() {
		try {
			logger.info("Jetty starting...");
			server.start();
			server.join();
			logger.info("Jetty started.");
		} catch (Exception e) {
			logger.error("Jetty starting failure: ", e);
			running = false;
			throw new RuntimeException(e);
		}
	}

	@Override
	public void finalize() throws Exception {
		if (server.isRunning()) {
			logger.info("Jetty stopping...");
			server.stop();
			logger.info("Jetty stopped.");
		}
	}

	public JettyStarter addBusInstance() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException,
			NoSuchMethodException, SecurityException {
		return addBusInstance(null);
	}

	public JettyStarter addBusInstance(String config) throws IllegalAccessException, IllegalArgumentException,
			InvocationTargetException, NoSuchMethodException, SecurityException {
		return addBusInstance(DEFAULT_CONTEXT_PATH, config, null);
	}

	public JettyStarter addBusInstance(String context, String config, Class<? extends BusServlet> servletClass)
			throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException,
			SecurityException {
		ServletHandler handler = new ServletHandler();
		Class<? extends BusServlet> clazz = null == servletClass ? this.scanServletClass() : servletClass;
		ServletHolder servlet = new ServletHolder(clazz);
		servlet.setDisplayName("Bus-Servlet[" + config + "]");
		clazz.getMethod("initializeBusParameters", ServletHolder.class, String.class).invoke(null, servlet, config);
		handler.addServletWithMapping(servlet, context);
		this.server.setHandler(handler);
		return this;
	}

	@SuppressWarnings("unchecked")
	private Class<? extends BusServlet> scanServletClass() {
		Set<Class<? extends BusServlet>> classes = ReflectionUtils.getSubClasses(BusServlet.class, "");
		for (Class<? extends BusServlet> c : classes)
			if (!c.getName().startsWith("net.butfly.bus."))
				return c;
		try {
			return (Class<? extends BusServlet>) Class.forName(Constants.Configuration.RECOMMEND_EXTERNAL_SERVLET_CLASSNAME);
		} catch (ClassNotFoundException e) {}
		try {
			return (Class<? extends BusServlet>) Class.forName(Constants.Configuration.RECOMMEND_INTERNAL_SERVLET_CLASSNAME);
		} catch (ClassNotFoundException ee) {}
		return null;
	}

	private Server createServer(int port, int thread) {
		Server jetty = new Server();
		SelectChannelConnector connector = new SelectChannelConnector();
		connector.setPort(port);
		connector.setMaxIdleTime(30000);
		jetty.setConnectors(new Connector[] { connector });

		jetty.addHandler(new SessionHandler(new HashSessionManager()));
		return jetty;
	}

	private static CommandLine parseCommandLine(String args[]) throws ParseException {
		Options options = new Options();
		options.addOption("p", "port", true, "Port of bus server (default 9876)");
		options.addOption("t", "thread", true, "Thread pool size of bus server (default 10, -1 for no thread pool in server)");
		options.addOption("c", "context", true, "Context path of bus server (default \"/bus\")");
		options.addOption("f", "config", true, "Config file of bus server (default bus-server.xml in root of classpath)");
		options.addOption("d", "jndi", true, "Properties file of JNDI datasource configuration");
		options.addOption("e", "servlet", true, "Servlet classname for bus starter.");
		options.addOption("k", "fork", true,
				"Whether the bus server run in a forked thread (not daemon, the server will be stopped on main thread stopping, default false)");
		options.addOption("h", "help", false, "Print help for command line sterter of bus server");
		CommandLineParser parser = new PosixParser();

		CommandLine cmd = parser.parse(options, args);
		if (cmd.hasOption('h')) {
			new HelpFormatter().printHelp("java <" + Thread.currentThread().getStackTrace()[1].getClassName() + "> [-option]",
					options);
			return null;
		} else
			return cmd;
	}

	@SuppressWarnings("unchecked")
	public static void main(String args[]) throws Exception {
		CommandLine cmd = parseCommandLine(args);
		if (null != cmd) {
			int port = cmd.hasOption('p') ? Integer.parseInt(cmd.getOptionValue('p')) : DEFAULT_PORT;
			int thread = cmd.hasOption('t') ? Integer.parseInt(cmd.getOptionValue('t')) : DEFAULT_THREAD_POOL_SIZE;
			String context = cmd.hasOption('c') ? cmd.getOptionValue('c') : DEFAULT_CONTEXT_PATH;
			String config = cmd.hasOption('f') ? cmd.getOptionValue('f') : null;
			String jdbc = cmd.hasOption('d') ? cmd.getOptionValue('d') : null;
			boolean fork = cmd.hasOption('k') ? Boolean.parseBoolean(cmd.getOptionValue('k')) : false;
			String servletClassname = cmd.getOptionValue('e');
			JettyStarter j = new JettyStarter(port, thread);
			Class<? extends BusServlet> clazz = null == servletClassname ? null : (Class<? extends BusServlet>) Class
					.forName(servletClassname);
			j.addBusInstance(context, config, clazz);
			if (null != jdbc)
				j.addJNDI(jdbc);
			j.run(fork);
		}
	}

	public boolean starting() {
		return this.server.isStarting();
	}

	@SuppressWarnings("unchecked")
	public void addJNDI(String contextXML) throws InstantiationException, IllegalAccessException, ClassNotFoundException,
			NamingException, DocumentException {
		URL url = Thread.currentThread().getContextClassLoader().getResource(contextXML);
		if (null == contextXML)
			return;
		for (Element resource : (List<Element>) new SAXReader().read(url).getRootElement().selectNodes("Resource")) {
			BeanMap map = new BeanMap(Class.forName(resource.attributeValue("type")).newInstance());
			Iterator<Attribute> it = resource.attributeIterator();
			while (it.hasNext()) {
				Attribute attr = it.next();
				if ("name".equals(attr.getName()) || "type".equals(attr.getName()))
					continue;
				if (String.class.equals(map.getType(attr.getName())))
					map.put(attr.getName(), attr.getValue());
				else if (int.class.equals(map.getType(attr.getName())) || Integer.class.equals(map.getType(attr.getName())))
					map.put(attr.getName(), Integer.parseInt(attr.getValue()));
				else if (boolean.class.equals(map.getType(attr.getName())) || Boolean.class.equals(map.getType(attr.getName())))
					map.put(attr.getName(), Boolean.parseBoolean(attr.getValue()));

			}
			new Resource("java:comp/env/" + resource.attributeValue("name"), map.getBean());
		}
	}
}